package dispatcher.gui.state;

import java.awt.event.ActionListener;
import java.net.InetAddress;
import java.util.Hashtable;
import java.util.Set;

import dispatcher.group.gui.floor.state.FloorDataCollection;
import dispatcher.group.gui.floor.state.FloorStruct;
import dispatcher.group.gui.listener.ElevatorInitializeEvent;
import dispatcher.group.gui.listener.ElevatorUpdateEvent;
import dispatcher.group.gui.listener.FloorInitializeEvent;
import dispatcher.group.gui.listener.PersonDispatcherPreInitConditionEvent;

import observer.ElevatorDataCollection;
import observer.ElevatorPartialStruct;
import state.Globals;
import state.group.ElevatorStruct;
import state.group.GroupDispatcherState;
import state.group.HallCall;
import state.group.events.EventCollection;

/**
 * This class holds a copy of the group dispatcher state. When updates occur on the group dispatcher
 * side, it will pass message to the GUI. At that given point of time, the GUI will then update its local 
 * copy.
 * @author Caleb
 *
 */
public class GroupDispatcherStateHandler extends StateHandler{
	
	private static final GroupDispatcherStateHandler instance = new GroupDispatcherStateHandler();
	
	/**
	 * Returns an instance of the group dispatcher state
	 */
	public static GroupDispatcherStateHandler getInstance(){
		return instance;
	}
	
	private ElevatorDataCollection elevatorCollection;
	private FloorDataCollection floorCollection;
	
	private int maxFloors;
	
	private GroupDispatcherState groupDispatcherInitialState;
	
	/**
	 * The main constructor
	 */
	private GroupDispatcherStateHandler(){
		super();
		floorCollection = new FloorDataCollection();
		elevatorCollection = new ElevatorDataCollection();
		maxFloors = 0;
	} 
	
	/**
	 * This will update the local copy with a list of events
	 */
	public void updateEvents(EventCollection collection){
		// Updates the elevator and floor collections
		synchronized(this){
			Globals.getLogger().log("Update with " + collection.size() + " Events", this.getClass().getName(), "updateEvents");
			System.out.println("Update with " + collection.size() + " Events");
			System.out.println("Event is " + collection.get(0).getEventType());
			elevatorCollection.updateStructsUsingEvents(collection);
			floorCollection.updateStructUsingEvents(collection);
		}
		
		// Notifies all listeners that an elevator event has occured
		for(ActionListener l : listener){
			l.actionPerformed(new ElevatorUpdateEvent(this, collection));
		}
	}
	
	
	/**
	 * This method will initialize the group dispatcher state
	 * @param state The state of the group dispatcher
	 */
	public void createInitialStatus(GroupDispatcherState state){
		
		int tmpMaxFloors;
		Set<Byte> elevatorKeySet;
		InetAddress address;
		int port;
		
		synchronized(this){
			// Copies all the vales into a local copy
			this.groupDispatcherInitialState = state;
			
			this.maxFloors = state.maxFloors;
			tmpMaxFloors = this.maxFloors;
			
			
			createElevatorInitialState(state.elevatorData);
			createFloorInitialState(state.hallCallMappings);
			
			elevatorKeySet = state.elevatorData.keySet();
			address = this.groupDispatcherInitialState.personDispatcherAddress;
			port = this.groupDispatcherInitialState.personDispatcherListeningPort;
		}
		
		// Notify all the action listeners that the state has been initialized
		for(ActionListener l : listener){
			l.actionPerformed(new ElevatorInitializeEvent(this, elevatorKeySet));
			l.actionPerformed(new FloorInitializeEvent(this, tmpMaxFloors));
			l.actionPerformed(new PersonDispatcherPreInitConditionEvent(this, address, port));
		}
	}
	
	/**
	 * This method will create and initialize all the floor states
	 * @param state The state of all the floors
	 */
	private synchronized void createFloorInitialState(Hashtable<HallCall, Byte> state){
		// Creates the different floors
		floorCollection.setMaxFloor(maxFloors);
		// Initializes all their states
		synchronized(state){
			Set<HallCall> floors = state.keySet();
			for(HallCall c : floors){
				floorCollection.get(c.getFloor()).updateStruct(c, state.get(c), true);
			}
		}
	}
	/**
	 * This method will create and initialize all the elevator states
	 * @param state The state of all the elevators
	 */
	private synchronized void createElevatorInitialState(Hashtable<Byte, ElevatorStruct> state){
		// This will delete the current amount of elevators
		elevatorCollection.clear();
		
		synchronized(state){
			// Copies the data into the newly create struct
			Set<Byte> keySet = state.keySet();
			System.out.println("Init with " + keySet.size() + " Elevators");
			for (byte b : keySet){
				ElevatorPartialStruct newStruct = new ElevatorPartialStruct();
				ElevatorStruct struct = state.get(b);
				synchronized(struct){
					newStruct.setIdentifier(struct.identifier);
		            newStruct.setDoorOpen(struct.doorOpen);
		            newStruct.setLastKnownFloor(struct.lastKnownFloor);
		            newStruct.setDirection(struct.direction);
				}
				elevatorCollection.put(b, newStruct);
			}
		}
	}
	
	public synchronized void resetHallCalls(){
		this.floorCollection.clear();
	}
	
	public synchronized void resetElevators(){
		elevatorCollection.clear();
	}
	
	/**
	 * This method will get the max number of floors
	 * @return Returns the max number of floors
	 */
	public synchronized int getMaxFloors(){
		return maxFloors;
	}
	
	/**
	 * This method will get an elevator status when passed in the elevator id
	 * @param elevatorNum The elevator ID
	 * @return Returns the elevator with the same ID
	 */
	public synchronized ElevatorPartialStruct getElevatorStatus(byte elevatorNum){
		return elevatorCollection.get(elevatorNum);
	}
	
	/**
	 * This method will get an floor status when passed in the floor id
	 * @param floorId The floor ID
	 * @return Returns the floor with the same ID
	 */
	public synchronized FloorStruct getFloorStatus(byte floorId){
		return floorCollection.get(floorId);
	}
}
